(function(window, document, $) {
    function Game2048(opt) {
        var prefix = opt.prefix, len = opt.len, size = opt.size, margin = opt.margin;
        var view = new View(prefix, len, size, margin);
        var board = new Board(len);

        view.init();
        board.init();
        board.onGenerate = function(e) {
            view.addNum(e.x, e.y, e.num);
        };
        board.onMove = function(e) {
            view.moveNum(e.fromX, e.fromY, e.toX, e.toY, e.num);
        };
        board.onMerge = function(e) {
            view.mergeNum(e.fromX, e.fromY, e.toX, e.toY, e.num);
        };
        board.onRemove = function(e) {
            view.removeNum(e.x, e.y);
        };
        board.onGameOver = function() {
            alert('游戏结束');
        };
        board.generate();
        board.generate();

        $(document).on('keydown', function(event) {
            switch (event.key) {
                case 'ArrowLeft':
                    board.moveLeft();
                    break;
                case 'ArrowRight':
                    board.moveRight();
                    break;
                case 'ArrowUp':
                    board.moveUp();
                    break;
                case 'ArrowDown':
                    board.moveDown();
                    break;
            }
        });
    }

    function View(prefix, len, size, margin) {
        this.prefix = prefix;
        this.len = len;
        this.size = size;
        this.margin = margin;
        this.container = $('#' + prefix + '_container');
        var containerSize = len * size + margin * (len + 1);
        this.container.css({ width: containerSize, height: containerSize });
        this.num = {};
    }

    View.prototype = {
        getPos: function(n) {
            return this.margin + n * (this.size + this.margin);
        },
        init: function() {
            for (var x = 0, len = this.len; x < len; ++x) {
                for (var y = 0; y < len; ++y) {
                    var $cell = $('<div class="' + this.prefix + '-cell"></div>');
                    $cell.css({
                        width: this.size + 'px',
                        height: this.size + 'px',
                        top: this.getPos(x),
                        left: this.getPos(y)
                    }).appendTo(this.container);
                }
            }
        },
        addNum: function(x, y, num) {
            var $num = $('<div class="' + this.prefix + '-num ' + this.prefix + '-num-' + num + '">');
            $num.text(num).css({
                width: 0,
                height: 0,
                lineHeight: this.size + 'px',
                top: this.getPos(x) + this.size / 2,
                left: this.getPos(y) + this.size / 2
            }).appendTo(this.container).animate({
                width: this.size + 'px',
                height: this.size + 'px',
                top: this.getPos(x),
                left: this.getPos(y)
            }, 100);
            this.num[x + '-' + y] = $num;
        },
        moveNum: function(fromX, fromY, toX, toY, num) {
            var $num = this.num[fromX + '-' + fromY];
            delete this.num[fromX + '-' + fromY];
            this.num[toX + '-' + toY] = $num;
            $num.animate({
                top: this.getPos(toX),
                left: this.getPos(toY)
            }, 200);
        },
        mergeNum: function(fromX, fromY, toX, toY, num) {
            var $num = this.num[fromX + '-' + fromY];
            delete this.num[fromX + '-' + fromY];
            this.num[toX + '-' + toY] = $num;
            $num.animate({
                top: this.getPos(toX),
                left: this.getPos(toY)
            }, 200, function() {
                $num.remove();
                var $newNum = $('<div class="' + this.prefix + '-num ' + this.prefix + '-num-' + num + '">');
                $newNum.text(num).css({
                    width: this.size + 'px',
                    height: this.size + 'px',
                    lineHeight: this.size + 'px',
                    top: this.getPos(toX),
                    left: this.getPos(toY),
                    opacity: 0
                }).appendTo(this.container).animate({ opacity: 1 }, 200);
                this.num[toX + '-' + toY] = $newNum;
            }.bind(this));
        },
        removeNum: function(x, y) {
            var $num = this.num[x + '-' + y];
            delete this.num[x + '-' + y];
            $num.remove();
        }
    };

    function Board(len) {
        this.len = len;
        this.arr = [];
        this.onGenerate = function() {};
        this.onMove = function() {};
        this.onMerge = function() {};
        this.onRemove = function() {};
        this.onGameOver = function() {};
    }

    Board.prototype = {
        init: function() {
            for (var arr = [], len = this.len, x = 0; x < len; ++x) {
                arr[x] = [];
                for (var y = 0; y < len; ++y) {
                    arr[x][y] = 0;
                }
            }
            this.arr = arr;
        },
        generate: function() {
            var empty = [];
            for (var x = 0, arr = this.arr, len = arr.length; x < len; ++x) {
                for (var y = 0; y < len; ++y) {
                    if (arr[x][y] === 0) {
                        empty.push({ x: x, y: y });
                    }
                }
            }
            if (empty.length < 1) {
                if (!this.canMove()) {
                    this.onGameOver();
                }
                return false;
            }
            var pos = empty[Math.floor((Math.random() * empty.length))];
            this.arr[pos.x][pos.y] = Math.random() < 0.5 ? 2 : 4;
            this.onGenerate({ x: pos.x, y: pos.y, num: this.arr[pos.x][pos.y] });
        },
        canMove: function() {
            for (var x = 0, len = this.arr.length; x < len; ++x) {
                for (var y = 0; y < len; ++y) {
                    if (this.arr[x][y] === 0) {
                        return true;
                    }
                    if (y < len - 1 && this.arr[x][y] === this.arr[x][y + 1]) {
                        return true;
                    }
                    if (x < len - 1 && this.arr[x][y] === this.arr[x + 1][y]) {
                        return true;
                    }
                }
            }
            return false;
        },
        moveLeft: function() {
            var moved = false;
            for (var x = 0, len = this.arr.length; x < len; ++x) {
                for (var y = 0, arr = this.arr[x]; y < len; ++y) {
                    for (var next = y + 1; next < len; ++next) {
                        if (arr[next] === 0) {
                            continue;
                        }
                        if (arr[y] === 0) {
                            this.onMove({ fromX: x, fromY: next, toX: x, toY: y, num: arr[next] });
                            arr[y] = arr[next];
                            arr[next] = 0;
                            moved = true;
                        } else if (arr[y] === arr[next]) {
                            this.onMerge({ fromX: x, fromY: next, toX: x, toY: y, num: arr[y] * 2 });
                            arr[y] *= 2;
                            arr[next] = 0;
                            moved = true;
                        }
                        break;
                    }
                }
            }
            if (moved) {
                this.generate();
            }
        },
        moveRight: function() {
            var moved = false;
            for (var x = 0, len = this.arr.length; x < len; ++x) {
                for (var y = len - 1, arr = this.arr[x]; y >= 0; --y) {
                    for (var next = y - 1; next >= 0; --next) {
                        if (arr[next] === 0) {
                            continue;
                        }
                        if (arr[y] === 0) {
                            this.onMove({ fromX: x, fromY: next, toX: x, toY: y, num: arr[next] });
                            arr[y] = arr[next];
                            arr[next] = 0;
                            moved = true;
                        } else if (arr[y] === arr[next]) {
                            this.onMerge({ fromX: x, fromY: next, toX: x, toY: y, num: arr[y] * 2 });
                            arr[y] *= 2;
                            arr[next] = 0;
                            moved = true;
                        }
                        break;
                    }
                }
            }
            if (moved) {
                this.generate();
            }
        },
        moveUp: function() {
            var moved = false;
            for (var y = 0, len = this.arr.length; y < len; ++y) {
                for (var x = 0; x < len; ++x) {
                    for (var next = x + 1; next < len; ++next) {
                        if (this.arr[next][y] === 0) {
                            continue;
                        }
                        if (this.arr[x][y] === 0) {
                            this.onMove({ fromX: next, fromY: y, toX: x, toY: y, num: this.arr[next][y] });
                            this.arr[x][y] = this.arr[next][y];
                            this.arr[next][y] = 0;
                            moved = true;
                        } else if (this.arr[x][y] === this.arr[next][y]) {
                            this.onMerge({ fromX: next, fromY: y, toX: x, toY: y, num: this.arr[x][y] * 2 });
                            this.arr[x][y] *= 2;
                            this.arr[next][y] = 0;
                            moved = true;
                        }
                        break;
                    }
                }
            }
            if (moved) {
                this.generate();
            }
        },
        moveDown: function() {
            var moved = false;
            for (var y = 0, len = this.arr.length; y < len; ++y) {
                for (var x = len - 1; x >= 0; --x) {
                    for (var next = x - 1; next >= 0; --next) {
                        if (this.arr[next][y] === 0) {
                            continue;
                        }
                        if (this.arr[x][y] === 0) {
                            this.onMove({ fromX: next, fromY: y, toX: x, toY: y, num: this.arr[next][y] });
                            this.arr[x][y] = this.arr[next][y];
                            this.arr[next][y] = 0;
                            moved = true;
                        } else if (this.arr[x][y] === this.arr[next][y]) {
                            this.onMerge({ fromX: next, fromY: y, toX: x, toY: y, num: this.arr[x][y] * 2 });
                            this.arr[x][y] *= 2;
                            this.arr[next][y] = 0;
                            moved = true;
                        }
                        break;
                    }
                }
            }
            if (moved) {
                this.generate();
            }
        }
    };

    window.Game2048 = Game2048;
})(window, document, jQuery);
